package fr.resasante.ldap;

import java.io.IOException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.SearchResult;

import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

import fr.resasante.ejb.dto.User;
import fr.resasante.exception.ApplicationException;
import fr.resasante.utils.CommonUtils;
import fr.resasante.utils.DepartmentsUtils;
import fr.resasante.utils.JobsUtils;
import fr.resasante.utils.WaysUtils;

/**
 * Object wrapping a LDAP entry
 * 
 * @author GEFFROY-DE-VILLEBLAN
 * 
 */
public class LdapObject {
	/**
	 * The user object
	 */
	private User user = null;

	/**
	 * Search result
	 */
	SearchResult sr = null;

	/**
	 * Constructor, initialize the values
	 * 
	 * @param sr
	 *            The search result
	 */
	public LdapObject() {
		user = new User();
	}

	/**
	 * Create an User object based on LDAP result
	 * 
	 * @param searchResult
	 *            The LDAP search result
	 */
	public void fillPhysicalPerson(SearchResult searchResult) {
		sr = searchResult;
		try {
			setRppsAdeliNumbers();
			user.setCity(CommonUtils.getInstance().CapitalizeName(getStringAttribute("st")));
			user.setCorporateName(CommonUtils.getInstance().CapitalizeName(
					getStringAttribute("raisonSociale")));
			setDepartment();
			user.setEnabled(false);
			setNames();
			setJob();
			user.setPassword(null);
			setPostalAdress();
			user.setPostalCode(getStringAttribute("postalCode"));
			user.setPostOfficeBox(getStringAttribute("postOfficeBox"));
			setGender();
			user.setUserType("Personne");
			setSpeciality();
		} catch (final NamingException e) {
			throw new ApplicationException(
					"Unable to get attribute in the search result from the LDAP server", e);
		}
	}

	/**
	 * Get an attribute value by its attribute name
	 * 
	 * @param attrName
	 *            The attribute name
	 * @return The attribute value
	 * @throws NamingException
	 */
	private String getStringAttribute(String attrName) throws NamingException {
		final Attribute attr = sr.getAttributes().get(attrName);
		if (null != attr) {
			return (String) attr.get();
		}
		return "";
	}

	/**
	 * Fill the department
	 * 
	 * @throws NamingException
	 */
	private void setDepartment() throws NamingException {
		final String departmentFromLdap = getStringAttribute("l");
		final Pattern p = Pattern.compile("\\((.*?)\\)", Pattern.DOTALL);
		final Matcher matcher = p.matcher(departmentFromLdap);
		String code = "";
		while (matcher.find()) {
			code = matcher.group(1);
		}
		final String label = departmentFromLdap.substring(0,
				departmentFromLdap.lastIndexOf('(') - 1).trim();

		if (!DepartmentsUtils.getInstance().isDepartmentExists(code, label)) {
			throw new ApplicationException("The department with code " + code + " and label "
					+ label + " is unknown");
		}
		user.setDepartmentCode(code);
		user.setDepartmentLabel(label);
	}

	/**
	 * Fill the gender
	 * 
	 * @throws NamingException
	 */
	private void setGender() throws NamingException {
		final String civilite = getStringAttribute("civilite");
		if ("Monsieur".equals(civilite)) {
			user.setGender("Homme");
		} else {
			user.setGender("Femme");
		}
	}

	/**
	 * Fill the job
	 * 
	 * @throws NamingException
	 */
	private void setJob() throws NamingException {
		final String jobFromLdap = getStringAttribute("personalTitle");
		if (!JobsUtils.getInstance().isJobExists(jobFromLdap)) {
			throw new ApplicationException("The job with label " + jobFromLdap + " is unknown");
		}
		user.setJob(jobFromLdap);
	}

	/**
	 * Fill the firstname and lastname
	 * 
	 * @throws NamingException
	 */
	private void setNames() throws NamingException {
		final String firstName = getStringAttribute("givenName");
		final String capitalizedFirstName = CommonUtils.getInstance()
				.CapitalizeName(firstName);
		user.setFirstname(capitalizedFirstName);
		final String lastName = getStringAttribute("sn");
		final String capitalizedLastName = CommonUtils.getInstance().CapitalizeName(lastName);
		user.setLastname(capitalizedLastName);
	}

	/**
	 * Fill the postal address
	 * 
	 * @throws NamingException
	 */
	private void setPostalAdress() throws NamingException {
		// remove the $ in the field
		String postalAddress = getStringAttribute("postalAddress").replace("$", "");
		// remove the corporate name in the address
		postalAddress = postalAddress.replace(user.getCorporateName().toUpperCase(), "");
		// for replacing ways abbreviations
		Map<String, String> ways = WaysUtils.getInstance().getAllWays();
		for (String abbr : ways.keySet()) {
			postalAddress = postalAddress.replaceAll(" " + abbr + " ", " " + ways.get(abbr)
					+ " ");
		}
		// Capitalize the address
		postalAddress = CommonUtils.getInstance().CapitalizeName(postalAddress.trim());
		user.setPostalAddress(postalAddress);
	}

	/**
	 * Fill the RPPS number, the ADELI number and the username
	 * 
	 * @throws NamingException
	 */
	private void setRppsAdeliNumbers() throws NamingException {
		Boolean isOldAccount = false;
		String adeliNumber = null;
		String rppsNumber = null;
		String username = "";
		String adeliNumberWithZero = getStringAttribute("oldIdNat");
		// If oldIdNat is not set
		if ("".equals(adeliNumberWithZero)) {
			adeliNumberWithZero = getStringAttribute("cn");
			if (adeliNumberWithZero.length() < 11) {
				isOldAccount = true;
			}
		}
		// If the ADELI number has a 0 prefix
		if (!"".equals(adeliNumberWithZero) && adeliNumberWithZero.length() == 10) {
			adeliNumber = adeliNumberWithZero.substring(1);
		}
		final String rppsNumberWithEight = getStringAttribute("cn");
		// If the RPPS number has 8 prefix
		if (!"".equals(rppsNumberWithEight) && rppsNumberWithEight.length() == 12) {
			rppsNumber = rppsNumberWithEight.substring(1);
		}
		user.setAdeliNumber(adeliNumber);
		// If the ADELI number is not null then the professional has a RPPS
		// number
		if (!isOldAccount) {
			user.setRppsNumber(rppsNumber);
			username = rppsNumber;
		}
		// If the ADELI number is null then the professional hasn't a
		// RPPS number
		else {
			user.setRppsNumber(null);
			username = adeliNumber;
		}
		user.setUsername(username);
	}

	/**
	 * Fill the speciality
	 * 
	 * @throws NamingException
	 */
	private void setSpeciality() throws NamingException {
		final String specialityFromLdap = getStringAttribute("specialite");
		if (StringUtils.isNotEmpty(specialityFromLdap)) {
			if (!JobsUtils.getInstance().isSpecialityExists(user.getJob(), specialityFromLdap)) {
				throw new ApplicationException("The speciality with label "
						+ specialityFromLdap + " is unknown");
			}
			user.setSpeciality(specialityFromLdap);
		}
	}

	/**
	 * Get a user in JSON format
	 * 
	 * @return The json string
	 */
	public String toJson() {
		final ObjectMapper mapper = new ObjectMapper();
		String jsonString;
		try {
			jsonString = mapper.writeValueAsString(user);
		} catch (final JsonGenerationException e) {
			throw new ApplicationException("Unable to generate the JSON string", e);
		} catch (final JsonMappingException e) {
			throw new ApplicationException("Unable to do the mapping for the JSON string", e);
		} catch (final IOException e) {
			throw new ApplicationException("Unable to get the JSON string", e);
		}
		return jsonString;
	}

	/**
	 * Get the user as known in database
	 * 
	 * @return The database user
	 */
	public User toUser() {
		return user;
	}
}
